﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace WWWSiteHelper
{

    public class ParseException : Exception
    {
        public ParseException() : base() { }
        public ParseException(string message) : base(message) { }
        public ParseException(SerializationInfo info, StreamingContext context) : base(info, context) { }
        public ParseException(string message, Exception innerException) : base(message, innerException) { }
    }

    public enum HTMLParserState
    {
        Text,
        TagBegin,
        TagName,
        TagNameDone,
        AttributeName,
        AttributeNameDone,
        AttributeEquals,
        AttributeExact,
        AttributeExactDone,
        AttributeValue,
        AttributeValueWithoutQuote,
        Backslash,
        CloseTag,
        CloseTagName,
        CloseTagNameDone,
        Comment0,   // <!
        Comment1,   // <!-
        Comment2,   // <!--
        Comment3,   // <!-- -
        Comment4,   // <!-- --
        Other,      // <!*
    }

    public class HTMLStateTransition
    {
        private static IDictionary<HTMLParserState, IList<KeyValuePair<Predicate<char>, HTMLParserState>>> rules
            = new Dictionary<HTMLParserState, IList<KeyValuePair<Predicate<char>, HTMLParserState>>>();

        static HTMLStateTransition()
        {
            Rule(HTMLParserState.Text, OpenTag, HTMLParserState.TagBegin);
            Rule(HTMLParserState.Text, True, HTMLParserState.Text);

            Rule(HTMLParserState.TagBegin, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.TagBegin, Char.IsWhiteSpace, HTMLParserState.TagBegin);
            Rule(HTMLParserState.TagBegin, Backslash, HTMLParserState.CloseTag);
            Rule(HTMLParserState.TagBegin, Char.IsLetter, HTMLParserState.TagName);
            Rule(HTMLParserState.TagBegin, Exclamation, HTMLParserState.Comment0);
            
            Rule(HTMLParserState.TagName, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.TagName, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.TagName, Char.IsWhiteSpace, HTMLParserState.TagNameDone);
            Rule(HTMLParserState.TagName, True, HTMLParserState.TagName);
            
            Rule(HTMLParserState.TagNameDone, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.TagNameDone, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.TagNameDone, Char.IsLetter, HTMLParserState.AttributeName);
            Rule(HTMLParserState.TagNameDone, Char.IsWhiteSpace, HTMLParserState.TagNameDone);
            
            Rule(HTMLParserState.AttributeName, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.AttributeName, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.AttributeName, Equals, HTMLParserState.AttributeEquals);
            Rule(HTMLParserState.AttributeName, Char.IsWhiteSpace, HTMLParserState.AttributeNameDone);
            Rule(HTMLParserState.AttributeName, True, HTMLParserState.AttributeName);

            Rule(HTMLParserState.AttributeEquals, Char.IsWhiteSpace, HTMLParserState.AttributeEquals);
            Rule(HTMLParserState.AttributeEquals, Quote, HTMLParserState.AttributeExact);
            Rule(HTMLParserState.AttributeEquals, True, HTMLParserState.AttributeValueWithoutQuote);

            Rule(HTMLParserState.AttributeValue, Quote, HTMLParserState.AttributeExactDone);
            Rule(HTMLParserState.AttributeValue, True, HTMLParserState.AttributeValue);
            
            Rule(HTMLParserState.AttributeValueWithoutQuote, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.AttributeValueWithoutQuote, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.AttributeValueWithoutQuote, Char.IsWhiteSpace, HTMLParserState.TagNameDone);
            Rule(HTMLParserState.AttributeValueWithoutQuote, True, HTMLParserState.AttributeValueWithoutQuote);
            
            Rule(HTMLParserState.AttributeNameDone, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.AttributeNameDone, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.AttributeNameDone, Char.IsLetter, HTMLParserState.AttributeName);
            Rule(HTMLParserState.AttributeNameDone, Equals, HTMLParserState.AttributeEquals);

            Rule(HTMLParserState.AttributeExactDone, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.AttributeExactDone, Backslash, HTMLParserState.Backslash);
            Rule(HTMLParserState.AttributeExactDone, Char.IsLetter, HTMLParserState.AttributeName);
            Rule(HTMLParserState.AttributeExactDone, Char.IsWhiteSpace, HTMLParserState.TagNameDone);

            Rule(HTMLParserState.AttributeExact, Quote, HTMLParserState.AttributeExactDone);
            Rule(HTMLParserState.AttributeExact, True, HTMLParserState.AttributeValue);

            Rule(HTMLParserState.Backslash, CloseTag, HTMLParserState.Text);

            Rule(HTMLParserState.CloseTag, Char.IsLetter, HTMLParserState.CloseTagName);
            Rule(HTMLParserState.CloseTag, Char.IsWhiteSpace, HTMLParserState.CloseTag);

            Rule(HTMLParserState.CloseTagName, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.CloseTagName, Char.IsWhiteSpace, HTMLParserState.CloseTagNameDone);
            Rule(HTMLParserState.CloseTagName, True, HTMLParserState.CloseTagName);
            
            Rule(HTMLParserState.CloseTagNameDone, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.CloseTagNameDone, Char.IsWhiteSpace, HTMLParserState.CloseTagNameDone);

            Rule(HTMLParserState.Comment0, Dash, HTMLParserState.Comment1);
            Rule(HTMLParserState.Comment0, True, HTMLParserState.Other);
            Rule(HTMLParserState.Comment1, Dash, HTMLParserState.Comment2);
            Rule(HTMLParserState.Comment2, Dash, HTMLParserState.Comment3);
            Rule(HTMLParserState.Comment2, True, HTMLParserState.Comment2);
            Rule(HTMLParserState.Comment3, Dash, HTMLParserState.Comment4);
            Rule(HTMLParserState.Comment3, True, HTMLParserState.Comment2);
            Rule(HTMLParserState.Comment4, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.Comment4, Dash, HTMLParserState.Comment4);
            Rule(HTMLParserState.Comment4, True, HTMLParserState.Comment2);

            Rule(HTMLParserState.Other, CloseTag, HTMLParserState.Text);
            Rule(HTMLParserState.Other, True, HTMLParserState.Other);
        }

        public static HTMLParserState Transition(HTMLParserState state, char symbol)
        {
            if (rules[state] != null)
            {
                foreach (KeyValuePair<Predicate<char>, HTMLParserState> predStateTuple in rules[state])
                {
                    //System.Console.WriteLine(predStateTuple.Key.GetType().Name + " " + symbol + " " + state + " " + predStateTuple.Key(symbol));
                    if (predStateTuple.Key(symbol))
                    {
                        return predStateTuple.Value;
                    }
                }
            }
            throw new ParseException();
        }

        private static void Rule(HTMLParserState currentState, Predicate<char> predicate, HTMLParserState resultState)
        {
            if (!rules.ContainsKey(currentState))
            {
                rules[currentState] = new List<KeyValuePair<Predicate<char>, HTMLParserState>>();
            }
            rules[currentState].Add(new KeyValuePair<Predicate<char>, HTMLParserState>(predicate, resultState));
        }

        private static bool True(char symbol)
        {
            return true;
        }

        private static bool OpenTag(char symbol)
        {
            return symbol == '<';
        }

        private static bool CloseTag(char symbol)
        {
            return symbol == '>';
        }

        private static bool Backslash(char symbol)
        {
            return symbol == '/';
        }

        private static bool Equals(char symbol)
        {
            return symbol == '=';
        }

        private static bool Quote(char symbol)
        {
            return symbol == '"';
        }

        private static bool Exclamation(char symbol)
        {
            return symbol == '!';
        }

        private static bool Dash(char symbol)
        {
            return symbol == '-';
        }
    }
}
